package com.hqd.ch03.v38.web.http.server;

import com.hqd.ch03.utils.LinkedCaseInsensitiveMap;
import com.hqd.ch03.v38.utils.StringUtils;
import com.hqd.ch03.v38.web.http.HttpHeaders;
import com.hqd.ch03.v38.web.http.HttpMethod;
import com.hqd.ch03.v38.web.http.InvalidMediaTypeException;
import com.hqd.ch03.v38.web.http.MediaType;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.*;

public class ServletServerHttpRequest implements ServerHttpRequest {

    protected static final Charset FORM_CHARSET = StandardCharsets.UTF_8;


    private final HttpServletRequest servletRequest;


    private URI uri;

    private HttpHeaders headers;

    public ServletServerHttpRequest(HttpServletRequest servletRequest) {
        this.servletRequest = servletRequest;
    }

    private static boolean isFormPost(HttpServletRequest request) {
        String contentType = request.getContentType();
        return (contentType != null && contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE) &&
                HttpMethod.POST.matches(request.getMethod()));
    }

    /**
     * 获取body中的内容
     */
    private static InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
        Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);

        Map<String, String[]> form = request.getParameterMap();
        for (Iterator<Map.Entry<String, String[]>> entryIterator = form.entrySet().iterator(); entryIterator.hasNext(); ) {
            Map.Entry<String, String[]> entry = entryIterator.next();
            String name = entry.getKey();
            List<String> values = Arrays.asList(entry.getValue());
            for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext(); ) {
                String value = valueIterator.next();
                writer.write(URLEncoder.encode(name, FORM_CHARSET.name()));
                if (value != null) {
                    writer.write('=');
                    writer.write(URLEncoder.encode(value, FORM_CHARSET.name()));
                    if (valueIterator.hasNext()) {
                        writer.write('&');
                    }
                }
            }
            if (entryIterator.hasNext()) {
                writer.append('&');
            }
        }
        writer.flush();

        return new ByteArrayInputStream(bos.toByteArray());
    }

    public HttpServletRequest getServletRequest() {
        return this.servletRequest;
    }

    @Override

    public HttpMethod getMethod() {
        return HttpMethod.resolve(this.servletRequest.getMethod());
    }

    @Override
    public String getMethodValue() {
        return this.servletRequest.getMethod();
    }

    @Override
    public URI getURI() {
        if (this.uri == null) {
            String urlString = null;
            boolean hasQuery = false;
            try {
                StringBuffer url = this.servletRequest.getRequestURL();
                String query = this.servletRequest.getQueryString();
                hasQuery = StringUtils.hasText(query);
                if (hasQuery) {
                    url.append('?').append(query);
                }
                urlString = url.toString();
                this.uri = new URI(urlString);
            } catch (URISyntaxException ex) {
                if (!hasQuery) {
                    throw new IllegalStateException(
                            "Could not resolve HttpServletRequest as URI: " + urlString, ex);
                }
                // Maybe a malformed query string... try plain request URL
                try {
                    urlString = this.servletRequest.getRequestURL().toString();
                    this.uri = new URI(urlString);
                } catch (URISyntaxException ex2) {
                    throw new IllegalStateException(
                            "Could not resolve HttpServletRequest as URI: " + urlString, ex2);
                }
            }
        }
        return this.uri;
    }

    @Override
    public HttpHeaders getHeaders() {
        if (this.headers == null) {
            this.headers = new HttpHeaders();
            for (Enumeration<?> names = this.servletRequest.getHeaderNames(); names.hasMoreElements(); ) {
                String headerName = (String) names.nextElement();
                for (Enumeration<?> headerValues = this.servletRequest.getHeaders(headerName);
                     headerValues.hasMoreElements(); ) {
                    String headerValue = (String) headerValues.nextElement();
                    this.headers.add(headerName, headerValue);
                }
            }

            try {
                MediaType contentType = this.headers.getContentType();
                if (contentType == null) {
                    String requestContentType = this.servletRequest.getContentType();
                    if (StringUtils.hasLength(requestContentType)) {
                        contentType = MediaType.parseMediaType(requestContentType);
                        if (contentType.isConcrete()) {
                            this.headers.setContentType(contentType);
                        }
                    }
                }
                if (contentType != null && contentType.getCharset() == null) {
                    String requestEncoding = this.servletRequest.getCharacterEncoding();
                    if (StringUtils.hasLength(requestEncoding)) {
                        Charset charSet = Charset.forName(requestEncoding);
                        Map<String, String> params = new LinkedCaseInsensitiveMap<>();
                        params.putAll(contentType.getParameters());
                        params.put("charset", charSet.toString());
                        MediaType mediaType = new MediaType(contentType.getType(), contentType.getSubtype(), params);
                        this.headers.setContentType(mediaType);
                    }
                }
            } catch (InvalidMediaTypeException ex) {
                // Ignore: simply not exposing an invalid content type in HttpHeaders...
            }
            if (this.headers.getContentLength() < 0) {
                int requestContentLength = this.servletRequest.getContentLength();
                if (requestContentLength != -1) {
                    this.headers.setContentLength(requestContentLength);
                }
            }
        }

        return this.headers;
    }

    @Override
    public Principal getPrincipal() {
        return this.servletRequest.getUserPrincipal();
    }

    @Override
    public InetSocketAddress getLocalAddress() {
        return new InetSocketAddress(this.servletRequest.getLocalAddr(), this.servletRequest.getLocalPort());
    }

    @Override
    public InetSocketAddress getRemoteAddress() {
        return new InetSocketAddress(this.servletRequest.getRemoteHost(), this.servletRequest.getRemotePort());
    }

    @Override
    public InputStream getBody() throws IOException {
        if (isFormPost(this.servletRequest)) {
            return getBodyFromServletRequestParameters(this.servletRequest);
        } else {
            return this.servletRequest.getInputStream();
        }
    }

}

